package com.starxg.m3u8.utils;

import com.starxg.m3u8.config.AppConfig;
import com.starxg.m3u8.config.ApplicationConfig;
import com.starxg.m3u8.config.WindowConfig;
import com.starxg.m3u8.exception.M3U8Exception;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * HttpUtils
 * @author huangxingguang@lvmama.com
 * @date 2020-08-06 12:40
 */
@Slf4j
public class HttpUtils {
    private static OkHttpClient okHttpClient;

    private static OkHttpClient initOkHttp() {

        if (okHttpClient == null) {
            synchronized (HttpUtils.class) {
                Duration timeout = Duration.ofMinutes(1);
                if (okHttpClient == null) {
                    Dispatcher dispatcher = new Dispatcher();
                    int max = AppConfig.HTTP_MAX_CONNECTIONS + 5;
                    dispatcher.setMaxRequestsPerHost(AppConfig.HTTP_MAX_CONNECTIONS + 5);
                    dispatcher.setMaxRequests(max);
                    OkHttpClient.Builder builder = new OkHttpClient.Builder();
                    try {
                        okHttpClient = trust(builder).connectTimeout(timeout.getSeconds(), TimeUnit.SECONDS).connectionPool(new ConnectionPool(max, timeout.getSeconds(), TimeUnit.SECONDS)).dispatcher(dispatcher).writeTimeout(timeout.getSeconds(), TimeUnit.SECONDS).readTimeout(timeout.getSeconds(), TimeUnit.SECONDS).followRedirects(true).followSslRedirects(true).build();
                    } catch (Exception e) {
                        log.error("初始化请求器失败。原因:{}", e.getMessage(), e);
                        System.exit(0);
                    }
                }
            }
        }

        return okHttpClient;
    }

    public static void close() {
        if (okHttpClient != null) {
            try {
                okHttpClient.dispatcher().executorService().shutdown();
                okHttpClient.connectionPool().evictAll();
                Cache cache = okHttpClient.cache();
                if (cache != null) {
                    cache.close();
                }
            } catch (IOException ignored) {
            }
        }
    }

    public static String get(String url) {
        return new String(getAsBytes(url));
    }

    public static byte[] getAsBytes(String url) {

        try {
            Response response = getHttpClient().newCall(prepareGet(new URL(url)).build()).execute();

            final int code = response.code();
            if (code != 200) {
                log.error("[{}] 请求失败，状态码不是 200 ，是：{}，response:{}", url, code, Objects.requireNonNull(response.body(), "body").string());
                throw new M3U8Exception("请求失败，状态码不是 200 ，是：" + code);
            }
            return Objects.requireNonNull(response.body(), "body").bytes();
        } catch (Exception e) {
            throw new M3U8Exception(e.getMessage());
        }
    }

    public static Request.Builder prepareGet(URL url) {
        return new Request.Builder().url(url);
    }

    public static Request.Builder prepareGet(String url) {
        try {
            return prepareGet(new URL(url));
        } catch (MalformedURLException e) {
            throw new M3U8Exception(e);
        }
    }

    private static OkHttpClient getHttpClient() {
        WindowConfig config = ApplicationConfig.getInstance().getWindow();
        OkHttpClient client = initOkHttp();

        if (!config.getProxyType().equals(WindowConfig.PROXY_NONE)) {

            OkHttpClient.Builder builder = client.newBuilder();
            Proxy.Type type = Proxy.Type.valueOf(config.getProxyType());
            Proxy proxy = new Proxy(type, new InetSocketAddress(config.getProxyHost(), Integer.parseInt(config.getProxyPort())));
            builder.proxy(proxy);

            if (StringUtils.isNotBlank(config.getProxyUsername())) {
                builder.proxyAuthenticator((route, response) -> {
                    String credential = Credentials.basic(config.getProxyUsername(), config.getProxyPassword());
                    return response.request().newBuilder().header("Proxy-Authorization", credential).build();
                });
            }

            client = builder.build();

        }

        return client;
    }

    public static Call enqueue(Request request, HttpCallback callback) {

        Call call = getHttpClient().newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                try {
                    callback.onFailure(call, e);
                } finally {
                    callback.completed();
                }
            }

            @Override
            public void onResponse(Call call, Response response) {
                try {
                    callback.onResponse(call, response);
                } catch (Exception e) {
                    callback.onFailure(call, e);
                } finally {
                    callback.completed();
                }
            }
        });
        return call;
    }

    public static Call enqueue(Request.Builder builder, HttpCallback callback) {
        return enqueue(builder.build(), callback);
    }

    private static OkHttpClient.Builder trust(OkHttpClient.Builder builder) throws Exception {
        // Create a trust manager that does not validate certificate chains
        final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[]{};
            }
        }};
        final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
        builder.hostnameVerifier((hostname, session) -> true);

        return builder;
    }
}
